/**
 * Title: DungeonMaster.java
 * Abstract: Project assignment. Represents java version of DungeonMaster.
 *   The game will use Swing and the background will make use of a JFrame. 
 *   Note: Must be run from command line since will not work in Eclipse or inside any Java IDE.
 *   use javac  to compile from command line
 *   Also, make sure all images are in same directory where you compile and run so program can find image files or game will not run.
 * Author: Jaime Moreno
 * 
 * Date: 5/11/11
 */


import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.applet.*; // need for playing sounds
import java.net.*; // need for loading sound from url

public class DungeonMasterJFrame extends JFrame implements KeyListener, 
                                                           MouseMotionListener
{
    // This holds the Swing components. A container is a subclass of JFrame.
    Container cont; // A generic Abstract Window Toolkit(AWT) container object is a component that can contain other AWT components. 
    
    //this is the current level:
    int currentLevel = 1;
    
    
    //this is the number of enemies:
    int numOfEnemies = 1;
    
    //this is the bullet's image:
    ImageIcon shipBullet = new ImageIcon("shipBullet.PNG");
    // For further information and examples of using image icons, see How to Use Icons  in The Java Tutorial.
    ImageIcon enemyBullet = new ImageIcon("enemyBullet.PNG");
    
    //this holds the player's bullets in an ArrayList of JLabels
    ArrayList<JLabel> playerBullets = new ArrayList<JLabel>();
    
    //this holds the enemies
    ArrayList<JLabel> enemies = new ArrayList<JLabel>();
    
    //this holds the bullets of the enemies
    ArrayList<JLabel> enemyBullets = new ArrayList<JLabel>();
    
    // background image
    ImageIcon dungeonImg = new ImageIcon("DungeonBackground.PNG");
    //these are the ship's images
    ImageIcon shipImg = new ImageIcon("ship.PNG");
    ImageIcon shipHit = new ImageIcon("shipHit.PNG");
    
    //these are the images of the enemies
    ImageIcon enemyImg = new ImageIcon("enemy.PNG"); // first invader 
    ImageIcon enemy2Img = new ImageIcon("enemy2.PNG");
    ImageIcon enemy3Img = new ImageIcon("enemy3.PNG");
    ImageIcon enemy4Img = new ImageIcon("enemy4.PNG"); // fourth invader
    ImageIcon enemyHit = new ImageIcon("enemyHit.PNG");
    
    //this is the powerup a special weapon that clears the screen of all enemies
    JLabel powerup = new JLabel(new ImageIcon("powerup.PNG"));
    
  //this is the background
    JLabel dungeon = new JLabel(dungeonImg);
    
    //this is the player's ship
    JLabel ship = new JLabel(shipImg);
    
    //the attack from a powerup
    JLabel powerAttack = new JLabel(new ImageIcon("powerAttack.PNG"));
    //this boolean keeps track of whether the power attack was used
    boolean useAttack = false;
    
    //the health
    JLabel healthPack = new JLabel(new ImageIcon("health.PNG"));
    
    //the score:
    int score = 0;
    
    //Lives
    int lives = 3; // start off with 3
    
    //the final score:
    int finalScore = 0;
    
    //the score and the level
    JLabel scoreLbl = new JLabel("Level: "+currentLevel+" Score: "+score+
            " Lives: "+lives);
    
    // for sounds
    // shoot is player, fly is aliens, hit is whenever alien or player hit
    AudioClip shoot, fly, hit;


    
    public DungeonMasterJFrame()
    {
        super("Dungeon Master by Jaime Moreno");
        setVisible(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(840,600);
        
        cont = getContentPane();
        cont.setLayout(null);
        
        //set the background color
        cont.setBackground(Color.GREEN);
        

        
        // loads player
    
        cont.add(ship);
        // Components added to a container are tracked in a list.
        // The order of the list will define the components' front-to-back stacking order within the container.
        // If no index is specified when adding a component to a container, it will be added to the end of the list (and hence to the         // bottom of the stacking order). 
        ship.setBounds(225, 550,50,50); // Moves and resizes this component. The new location of the top-left corner is specified by x and                                         // y, and the new size is specified by width and height. 
        
        // loads player score label at bottom of screen
        scoreLbl.setFont(new Font("Times New Roman", Font.BOLD, 20));
        scoreLbl.setForeground(Color.WHITE);
        cont.add(scoreLbl);
        scoreLbl.setBounds(20,600,400,50);
        
        // load sounds 
        // need to be in try/catch block
            try {
                shoot = Applet.newAudioClip(new URL("file:ArrowShoot.wav"));
                fly = Applet.newAudioClip(new URL("file:SwordSlash1.wav"));
                hit = Applet.newAudioClip(new URL("file:hit.wav"));
              } catch (MalformedURLException e) {}

        // display intro screen
             
              Title title = new Title();
              title.start(); // display intro
              
              
        // load background
        cont.add(dungeon);
        dungeon.setBounds(0, 0, 840, 600);
        
        
        
        // get keyboard and mouse events
        addKeyListener(this);
        addMouseMotionListener(this);
        
        // load first wave of enemies
        populateEnemies();
        
        // start game by starting animation thread
        Play play= new Play();
        play.start();
        
        setContentPane(cont);
        
    }
    
    public class Title extends Thread
    {
    	   public void run()
           {
               JLabel results = new JLabel("DUNGEON MASTER by Jaime Moreno");
               cont.add(results);
               results.setBounds(75,250,800,500);
               results.setFont(new Font("Courier New", Font.BOLD, 40));
               results.setForeground(Color.yellow);
           }
            
 
           
    }
    
    public void populateEnemies()
    {
        for(int i = 0; i <= numOfEnemies; i++)
        {
            JLabel tempEnemy = new JLabel(enemyImg);
            // random() Returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0. Returned values are             // chosen pseudorandomly with (approximately) uniform distribution from that range
            int randLocation = (int)(Math.random()*700); // need cast since double to int

            tempEnemy.setBounds(randLocation,10,30,30); // will appear in random location on horizontal line
            // add temp JLabel to enemy arraylist and to container or JFrame
            enemies.add(tempEnemy);
            cont.add(enemies.get(i));
            cont.setComponentZOrder((enemies.get(i)),0);
        }
    }

    public void populateEnemies2()
    {
        for(int i = 0; i <= numOfEnemies; i++)
        {
            JLabel tempEnemy = new JLabel(enemy2Img);
            int randLocation = (int)(Math.random()*700);
            enemies.add(tempEnemy);
            cont.add(enemies.get(i));
            tempEnemy.setBounds(randLocation,10,30,30);
            cont.setComponentZOrder((enemies.get(i)),0);
        }
    }
    
        public void populateEnemies3()
    {
        for(int i = 0; i <= numOfEnemies; i++)
        {
            JLabel tempEnemy = new JLabel(enemy3Img);
            int randLocation = (int)(Math.random()*700);
            enemies.add(tempEnemy);
            cont.add(enemies.get(i));
            tempEnemy.setBounds(randLocation,10,30,30);
            cont.setComponentZOrder((enemies.get(i)),0);
        }
    }

    public void populateEnemies4()
    {
        for(int i = 0; i <= numOfEnemies; i++)
        {
            JLabel tempEnemy = new JLabel(enemy4Img);
            int randLocation = (int)(Math.random()*700);
            enemies.add(tempEnemy);
            cont.add(enemies.get(i));
            tempEnemy.setBounds(randLocation,10,30,30);
            cont.setComponentZOrder((enemies.get(i)),0);
        }
    }
    
    public class Play extends Thread
    {
        public void run()
        {
            while(true) // main game loop
            {
                try
                {
                    for(int i = 0; i < enemies.size(); i++)
                    {
                        JLabel tempEnemy = enemies.get(i);
                        int distance = (int)(Math.random()*2);
                        tempEnemy.setBounds(tempEnemy.getX(),
                                tempEnemy.getY()+distance,30,30);
                        
                        //check if the power attack hit the aliens
                        if(useAttack)
                        {
                            if(powerAttack.getBounds().intersects(
                                                        tempEnemy.getBounds()))
                            {
                                cont.remove(tempEnemy);
                                i--;
                                numOfEnemies--;
                                score+=15;
                            }
                        }
                        
                        if(tempEnemy.getBounds().intersects(ship.getBounds()))
                        {
                            lives--;
                            cont.remove(tempEnemy);
                        }
                        
                        if(tempEnemy.getY()>550)
                            tempEnemy.setBounds(tempEnemy.getX(), 10, 30, 30);
                        
                        int fire = (int)(Math.random()*2500);
                        if(fire<=1) // so all aliens don't fire at same time
                        {
                            JLabel tempBullet = new JLabel(enemyBullet);
                            tempBullet.setBounds(tempEnemy.getX()+5,
                                                tempEnemy.getY()+30,10,20);
                            enemyBullets.add(tempBullet);
                            cont.add(enemyBullets.get( enemyBullets.size()-1));
                            cont.setComponentZOrder(enemyBullets.get(enemyBullets.size()-1),0);
                            // play alien shooting sound
                            fly.play();
                        }
                    }
                    
                    //check if the player's bullets hit the aliens

                    boolean breakAll = false; // use to break early from loop if all aliens gone from screen

                    for(int i = 0; i < playerBullets.size(); i++)
                    {
                        JLabel temp = (playerBullets.get(i));
                        temp.setBounds(temp.getX(),temp.getY()-8,10,20);
                        
                        if(temp.getY()<0)
                        {
                            cont.remove(temp);
                            playerBullets.remove(i);
                            i--;
                        }
                        
                        for(int j = 0; j < enemies.size(); j++)
                        {
                            JLabel tempEnemy = enemies.get(j);
                            if(temp.getBounds().intersects(
                                    tempEnemy.getBounds()))
                            {
                                score+=1000;
                                tempEnemy.setIcon(enemyHit); // display dead alien for 1/10 second
                                hit.play();  // play explosion sound
                                Thread.sleep(100); // Causes the currently executing thread to sleep (temporarily cease execution) for the                                                    // specified number of milliseconds. This is what is used to create animation.
                                enemies.remove(j);
                                cont.remove(tempEnemy);
                                numOfEnemies--;

                                if(numOfEnemies<=0) // player cleared screen of all enemies so increase level
                                {
                                    currentLevel++;
                                    
                                    if(currentLevel%3==0)
                                    {
                                        cont.add(powerup);
                                        int randLoc = (int)(Math.random()*450);
                                        powerup.setBounds(randLoc,0,30,30);
                                    }
                                    if(currentLevel%5==0)
                                    {
                                        cont.add(healthPack);
                                        int randLoc = (int)(Math.random()*450);
                                        healthPack.setBounds(randLoc,0,30,30);
                                    }
                                    
                                    numOfEnemies = currentLevel * currentLevel;
                                    

                                    if(currentLevel%4==0) {
                                    populateEnemies();
                                    }
                                    
                                    if(currentLevel%4== 1) {
                                    populateEnemies2();
                                    }
                                    
                                    if(currentLevel%4 == 2) {
                                    populateEnemies3();
                                    }
                                    
                                    if(currentLevel%4 == 3) {
                                    populateEnemies4();
                                    }

                                    breakAll = true; // stop checking for collisions since no more aliens
                                    break;
                                }
                            }
                        }
                        if(breakAll)
                            break;
                    }
                    
                    //move the power attack
                    if(useAttack)
                    {
                        powerAttack.setBounds(0,powerAttack.getY()-1,500,10);
                        if(powerAttack.getY()<0)
                        {
                            cont.remove(powerAttack);
                            useAttack = false;
                            currentLevel++;
                            numOfEnemies = currentLevel * currentLevel;
                            
                                    if(currentLevel%4==0) {
                                    populateEnemies();
                                    }
                                    
                                    if(currentLevel%4== 1) {
                                    populateEnemies2();
                                    }
                                    
                                    if(currentLevel%4 == 2) {
                                    populateEnemies3();
                                    }
                                    
                                    if(currentLevel%4 == 3) {
                                    populateEnemies4();
                                    }
                        }
                    }
                    
                    // every third level, allow the powerup to be moved
                    
                    if(currentLevel%3==0)
                    {
                        powerup.setBounds(powerup.getX(),powerup.getY()+1,30,30);
                        if(powerup.getBounds().intersects(ship.getBounds()))
                        {
                            useAttack = true;
                            cont.add(powerAttack);
                            powerAttack.setBounds(0,ship.getY(),500,10);
                            cont.remove(powerup);
                            powerup.setBounds(-200,-200,30,30);
                        }
                    }
                    
                    if(currentLevel%5==0)
                    {
                        healthPack.setBounds(healthPack.getX(),
                                                    healthPack.getY()+1,30,30);
                        if(healthPack.getBounds().intersects(ship.getBounds()))
                        {
                            lives+=1; // get new life
                            score+=100;
                            cont.remove(healthPack);
                            healthPack.setBounds(-100,-100,30,30);
                        }
                    }
                    
                    //check if the aliens' bullets hit the player
                    
                    breakAll = false;
                    for(int i = 0; i < enemyBullets.size(); i++)
                    {
                        JLabel temp = enemyBullets.get(i);
                        temp.setBounds(temp.getX(),temp.getY()+2,10,20);
                        
                        if(temp.getY()>550) // bullet is past bottom of screen so remove from screen then from arraylist
                        {
                            cont.remove(temp);
                            enemyBullets.remove(i);
                            i--;
                        }

                        if(ship.getBounds().intersects(temp.getBounds())) // we got a hit
                        {
                            ship.setIcon(shipHit);
                            hit.play(); // play explosion sound
                            Thread.sleep(100);
                            ship.setIcon(shipImg);
                            // decrease player score and life
                            score-=100;
                            lives-=1;
                            cont.remove(temp);
                            enemyBullets.remove(i);
                            
                            numOfEnemies--;
                            if(numOfEnemies<=0)
                            {
                                currentLevel++;
                                numOfEnemies = currentLevel * currentLevel;

                                
                                    if(currentLevel%4==0) {
                                    populateEnemies();
                                    }
                                    
                                    if(currentLevel%4== 1) {
                                    populateEnemies2();
                                    }
                                    
                                    if(currentLevel%4 == 2) {
                                    populateEnemies3();
                                    }
                                    
                                    if(currentLevel%4 == 3) {
                                    populateEnemies4();
                                    }
                                breakAll = true;
                                break;
                            }
                        }
                        
                        if(breakAll)
                            break;
                    }
                    
                    cont.repaint(); // updates all the graphics
                    Thread.sleep(10);
                    scoreLbl.setText("Level: "+currentLevel+" Score: "+score+
                                                " Lives: "+lives);
                    scoreLbl.setForeground(Color.yellow);
                    
                    if(lives<=0)
                    {
                        finalScore = score;
                        Scroll scroll = new Scroll();
                        scroll.start(); // display final score screen
                        break; // breaks out of main game loop so game ends here
                    }
                }
                catch(Exception e){}
            }
        }
    }
    
    public class Scroll extends Thread
    {
        public void run()
        {
            JLabel results = new JLabel("GAME OVER!!!  FINAL SCORE: "+finalScore);
            cont.add(results);
            results.setBounds(500,10,10000,500);
            results.setFont(new Font("Courier New", Font.BOLD, 400));
            results.setForeground(Color.yellow);
            
            while(true)
            {
                try
                {
                    results.setBounds(results.getX()-10,10,10000,500);
                    cont.setComponentZOrder(results,0);
                    Thread.sleep(30);
                    
                    if(results.getX()<-10000)
                        results.setBounds(500,10,10000,500);
                }
                catch(Exception e){}
            }
        }
    }
    
    public void mouseMoved(MouseEvent event)
    {
        // move ship to match mouse movement
        ship.setBounds(event.getX()-25,event.getY()-40,50,50);
    }
    
    public void mouseDragged(MouseEvent event){}

    public void keyReleased(KeyEvent event) {} // not used but must be included because of KeyListener interface
    
    public void keyTyped(KeyEvent event) {}  // not used but must be included because of KeyListener interface
    
    public void keyPressed(KeyEvent event)
    {
       int keyCode = event.getKeyCode();
   
       switch(keyCode) { // using switch here so it's easier to add other keys in future

        // ctrl, enter or space can be used to fire weapon
       
        case KeyEvent.VK_ENTER:
        case KeyEvent.VK_SPACE:
        case KeyEvent.VK_CONTROL:
        {
            JLabel tempBullet = new JLabel(shipBullet);
            // set bullet position to match ship so it looks like bullet firing from ship
            tempBullet.setBounds(ship.getX()+20,ship.getY()-20,10,20);
            playerBullets.add(tempBullet);
            cont.add(playerBullets.get(playerBullets.size()-1));
            /* The z-order determines the order that components are painted; the component with the highest z-order paints first and the component with the lowest z-order paints last. Where components overlap, the component with the lower z-order paints over the component with the higher z-order. So this means bullets will show up on top here. You can see this if you fire over score label. Ship and bullet stay on top of them.
*/
            cont.setComponentZOrder( playerBullets.get(playerBullets.size()-1),0);
            score-=2;
            
            // play shooting sound
            shoot.play();
            break;
        }
      } // end switch

    }
     
    public static void main (String[] args)
    {
        new DungeonMasterJFrame();
    }
}



